#include <QDebug>
#include <QtOpenGL>
#include <QPoint>
#include <QFileDialog>
#include <QImage>

#include "rendercontroller.h"
#include "GLDisplay.h"
#include "mainwindow.h"
#include "Object3D.h"
#include "cubo.h"
#include "sphere.h"
#include "cylinder.h"
#include "teapot.h"
#include "torus.h"
#include "icosahedron.h"
#include "octahedron.h"
#include "dodecahedron.h"
#include "tetrahedron.h"
#include "cone.h"
#include "Arcball.h"
#include "DirectionalLight.h"

RenderController::RenderController(MainWindow *mainWindow, QObject *parent): QObject(parent)
{
    light = new DirectionalLight;
    arcBall = new ArcBall(500);
    wireframe = false;
    lightRotation = false;

    this->display = new GLDisplay();
    mainWindow->setGLDisplay(display);

    {  // esta ordem deve ser mantida
        display->updateGL();

        objects.append(new Cubo());
        objects.append(new Sphere());
        objects.append(new Cylinder());

        //cubo = new Cubo();
        //sphere = new Sphere();
        //cylinder = new Cylinder();

        objects[0]->setTraslation(QVector3D(0.0, 0.0, 0.0));
        objects[1]->setTraslation(QVector3D(2,0.0, 0.0));
        objects[2]->setTraslation(QVector3D(-2, 0.0, 0.0));

        objects[0]->setScaleFactor(25);
        objects[1]->setScaleFactor(25);
        objects[2]->setScaleFactor(25);

        model = NULL;
        //model = objects[0];
        //model->setSelected(true);

        connect(display, SIGNAL(drawModel()), this, SLOT(drawModel()));
        connect(display, SIGNAL(lightSetup()), this, SLOT(lightSetup()));
    }

    connect(display, SIGNAL(mouseLefthFinish(QPoint,QPoint)), this, SLOT(mouseLefthFinish(QPoint,QPoint)));
    connect(display, SIGNAL(mouseLeftMove(QPoint,QPoint)), this, SLOT(mouseLeftMove(QPoint,QPoint)));
    connect(display, SIGNAL(mouseRigthFinish(QPoint,QPoint)), this, SLOT(mouseRigthFinish(QPoint,QPoint)));
    connect(display, SIGNAL(mouseRigthMove(QPoint,QPoint)), this, SLOT(mouseRigthMove(QPoint,QPoint)));
    connect(display, SIGNAL(mouseCancel()), this, SLOT(mouseCancel()));

    connect(mainWindow, SIGNAL(wireframeClicked(bool)), this, SLOT(wireFrameToggle(bool)));
    connect(mainWindow, SIGNAL(saveResultAsImage()), this, SLOT(saveResultAsImage()));
    connect(mainWindow, SIGNAL(lightRotationToggle(bool)), this, SLOT(lightRotationToggle(bool)));

    connect(mainWindow, SIGNAL(changeObjectSelected(QString)), this, SLOT(selectObject(QString)));
    connect(mainWindow, SIGNAL(toggleShader(bool)), this, SLOT(toggleShader(bool)));
    connect(mainWindow, SIGNAL(colorChanged(QColor)), this, SLOT(changeModelColor(QColor)));
    connect(mainWindow, SIGNAL(scaleFactorChanged(int)), this, SLOT(changeScaleFactorValue(int)));
    connect(mainWindow, SIGNAL(textureChanged(QString)), this, SLOT(changeModelTexture(QString)));
    connect(mainWindow, SIGNAL(insertObjectCliked(QString)), this, SLOT(insertObject(QString)));

    connect(mainWindow, SIGNAL(updateRender()), this, SLOT(updateGL()));
    connect(mainWindow, SIGNAL(deletePressed()), this, SLOT(deleteModel()));

    connect(this, SIGNAL(objectChanged(bool)), mainWindow, SLOT(toggleCheckbox(bool)));
    connect(this, SIGNAL(objectSelectedScale(int)), mainWindow, SLOT(changeScaleSliderValue(int)));

    mainWindow->show();
}

RenderController::~RenderController()
{
    foreach (Object3D * object, objects) {
        delete object;
    }
    //delete cubo;
    //delete sphere;
    //delete cylinder;
    delete display;
    delete arcBall;
}

void RenderController::updateGL(void)
{
    display->updateGL();
}

void RenderController::lightSetup(void)
{
    light->draw();
}

void RenderController::drawModel(void)
{
    if(wireframe)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    else
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    foreach (Object3D * object, objects) {
        object->draw();
    }
    //cubo->draw();
    //sphere->draw();
    //cylinder->draw();

}

void RenderController::mouseRigthMove(QPoint begin, QPoint curr)
{
    if (model != NULL) {
        QVector3D v(curr-begin);
        model->setTraslation(v/40.0);
        display->updateGL();
    }
}

void RenderController::mouseRigthFinish(QPoint ini, QPoint curr)
{
    if (model != NULL) {
        QVector3D v(curr-ini);
        model->setTraslation(v/40.0);
        //model->setTraslation(QVector3D());
        //model->setCenter(model->center() + (v/40.0));
        display->updateGL();
    }
}

void RenderController::mouseLeftMove(QPoint ini, QPoint curr)
{
    if(ini == curr)
        return;
    QPoint center(display->width()/2, display->height()/2);

    if (lightRotation)
        light->setInteractiveQuartenion(arcBall->rotationForPoints(center,curr, ini));
    else if (model != NULL)
        model->setInteractiveQuartenion(arcBall->rotationForPoints(center,curr, ini));
    else
        display->setInteractiveQuartenion(arcBall->rotationForPoints(center,curr, ini));

    display->updateGL();
}

void RenderController::mouseLefthFinish(QPoint ini, QPoint curr)
{
    if (ini == curr)
    {
        QVector3D eye = display->getEye();
        QVector3D lookAt = display->getLookAt();
        QVector3D up = display->getUp();
        // por enquanto esses parametro estao estatico.. mas se qd a gente colocar duas cameras tem que mudar
        pickingRay.setVectors(eye.x(),eye.y(),eye.z(), lookAt.x(),lookAt.y(),lookAt.z(), up.x(),up.y(),up.z());
        pickingRay.picking(ini.x(), ini.y(), display->width(), display->height(), 11.5, 5.0);

        Object3D * picked = pickingRay.throwRay(objects);

        if (model != NULL)
            model->setSelected(false);

        if (picked != NULL) {
            model = picked;
            model->setSelected(true);
        } else {
            model = NULL;
        }

        display->updateGL();
        return;
    }

    QPoint center(display->width()/2, display->height()/2);
    QQuaternion t = arcBall->rotationForPoints(center,curr, ini);
    if (lightRotation)
    {
        light->addRotation(t);
        light->setInteractiveQuartenion(QQuaternion());
    }
    else if (model != NULL)
    {
        model->addRotation(t);
        model->setInteractiveQuartenion(QQuaternion());
    }
    else
    {
        display->salvarEyeRotation();
    }

    display->updateGL();
}

void RenderController::mouseCancel()
{
    if(lightRotation)
        light->setInteractiveQuartenion(QQuaternion());
    else if (model != NULL)
        model->setInteractiveQuartenion(QQuaternion());
    else
        display->salvarEyeRotation();

    display->updateGL();
}

void RenderController::wireFrameToggle(bool wireframe)
{
    this->wireframe = wireframe;
    display->updateGL();
}

void RenderController::saveResultAsImage()
{
    QString filePath = saveImage();

    if(filePath.isEmpty())
        return;

    display->updateGL();
    display->grabFrameBuffer().save(filePath);
}

QGLWidget* RenderController::getGLContext(void)
{
    return display;
}

void RenderController::lightRotationToggle(bool lt)
{
    lightRotation = lt;
}

QVector3D RenderController::getLightPosition() const
{
    return light->getLightPosition();
}

QString RenderController::saveImage()
{
    static QString dir = ".";
    QString filename = QFileDialog::getSaveFileName(0, "Save Image", dir, "*.png");
    QString ext = ".png";

    if(filename == ext || filename.isEmpty())
        return QString();

    QFileInfo fi(filename);
    dir = fi.absolutePath();

    if(filename.right(ext.length()) != ext)
        filename += ext;

    return filename;
}

void RenderController::selectObject(QString name)
{
    if (model != NULL)
        model->setSelected(false);
    if (QString::compare(name, "Cubo") == 0) {
        model = objects[0];
    } else if (QString::compare(name, "Cilindro") == 0) {
        model = objects[2];
    } else if (QString::compare(name, "Esfera") == 0) {
        model = objects[1];
    }
    model->setSelected(true);

    emit objectChanged(model->getShaderOn());
    emit objectSelectedScale(model->getScaleFactor());

    display->updateGL();
}

void RenderController::insertObject(QString objectType) {
    if (QString::compare(objectType, "Cubo") == 0) {
        objects.append(new Cubo());
    } else if (QString::compare(objectType, "Cilindro") == 0) {
        objects.append(new Cylinder());
    } else if (QString::compare(objectType, "Esfera") == 0) {
        objects.append(new Sphere());
    } else if (QString::compare(objectType, "Teapot") == 0) {
        objects.append(new Teapot());
    } else if (QString::compare(objectType, "Torus") == 0) {
        objects.append(new Torus());
    } else if (QString::compare(objectType, "Icosaedro") == 0) {
        objects.append(new Icosahedron());
    } else if (QString::compare(objectType, "Octaedro") == 0) {
        objects.append(new Octahedron);
    } else if (QString::compare(objectType, "Tetraedro") == 0) {
        objects.append(new Tetrahedron());
    } else if (QString::compare(objectType, "Dodecaedro") == 0) {
        objects.append(new Dodecahedron());
    } else if (QString::compare(objectType, "Cone") == 0) {
        objects.append(new Cone());
    }
    display->updateGL();
}

void RenderController::toggleShader(bool toggle)
{
    if (model != NULL) {
        model->toggleShader(toggle);
        display->updateGL();
    }
}

void RenderController::changeModelColor(QColor color)
{
    if (model != NULL) {
        model->setColor(color);
        display->updateGL();
    }
}

void RenderController::changeScaleFactorValue(int value)
{
    if (model != NULL) {
        model->setScaleFactor(value);
        display->updateGL();
    }
}

void RenderController::changeModelTexture(QString filename)
{
    if (model != NULL) {
        GLuint texture = display->bindTexture(filename, GL_TEXTURE_2D, GL_RGBA);
        model->setTexture(texture);
        display->updateGL();
    }
}

void RenderController::deleteModel()
{
    if (model != NULL) {
        int modelIndex = objects.indexOf(model);
        objects.remove(modelIndex);
        delete model;
        model = NULL;
        display->updateGL();
    }
}
